/*
 * Type-in <select> scripts.
 *
 * @author Li Yang (young-lee@21cn.com)
 */

function Ytypein() {
}

Ytypein.MAX_MATCHES = 50;

Ytypein.TYPEIN_DIV_CONTENT =
	"<div style='color:#FFF; background:#888; padding:3px; white-space:nowrap;'>" +
		"<small>Find: </small><input id='typein_pattern' style='width:6em; border:0; background:transparent; color:#FFF;'/>" +
	"</div>" +
	"<div id='typein_matches' style='padding:3px; cursor:default;'></div>";

Ytypein.enableTypeInFunction = function() {
	if (!$("typein_div")) {
		// create typein div
		var div = document.createElement("div");
		div.setAttribute("id", "typein_div");
		div.style.position = "absolute";
		div.style.zIndex = "1000";
		div.style.left = "0px";
		div.style.top = "0px";
		div.style.display = "none";
		div.style.background = "#FFF";
		div.style.border = "solid 1px #000";
		document.body.appendChild(div);

		// content of typein div
		div.innerHTML = Ytypein.TYPEIN_DIV_CONTENT;

		// create a <iframe> shim
		// IE needs this to display <div> on top of <select> properly
		var shim = document.createElement("iframe");
		shim.setAttribute("id", "typein_shim");
		shim.setAttribute("scrolling", "no");
		shim.setAttribute("frameborder", "0");
		shim.style.position = "absolute";
		shim.style.zIndex = "999";
		shim.style.left = "0px";
		shim.style.top = "0px";
		shim.style.display = "none";
		document.body.appendChild(shim);

		// register listener on document
		Event.observe(document.body, "keypress", Ytypein.globalOnKeyPress, true);
		Event.observe(document.body, "keydown", Ytypein.globalOnKeyDown, true);
		Event.observe($("typein_pattern"), "blur", Ytypein.leaveTypeIn);
	}
}

Ytypein.globalOnKeyPress = function(event) {
	var eventSrc = Event.element(event);
	var keyCode = event.keyCode || event.charCode;

	// in typein mode?
	if (Ytypein.isTypeIn()) {
		if (eventSrc.id == 'typein_pattern')
			window.setTimeout('Ytypein.rematchAll();', 50);
		return;
	}

	// catch key from a <select>?
	if ("select".isTagOf(eventSrc)) {
		// should enter typein mode?
		if (keyCode == 32 && !event.ctrlKey && !event.altKey) {
			// space key
			Event.stop(event);
			Ytypein.enterTypeIn(eventSrc);
			return;
		}
		if (Yutil.isChar(event)) {
			Event.stop(event);
			Ytypein.enterTypeIn(eventSrc, String.fromCharCode(Yutil.charCode(event)));
			return;
		}
	}
}

Ytypein.globalOnKeyDown = function(event) {
	// some special keys can only be caught by onkeydown,
	// we have to process them here

	var eventSrc = Event.element(event);
	if (!Ytypein.isTypeIn() || eventSrc.id != 'typein_pattern')
		return; // if not in typein mode
	
	var keyCode = event.keyCode || event.charCode;

	// is tab or enter?
	if (keyCode == 9 || keyCode == 10 || keyCode == 13) {
		// leave typein mode
		Ytypein.leaveTypeIn();
		return;
	}

	// is ESC?
	if (keyCode == 27) {
		// leave typein mode, discard any change
		Event.stop(event);
		var div = $("typein_div");
		div.optionIndex = -1;
		div.matchIndex = -1;
		Ytypein.leaveTypeIn();
		return;
	}

	// is up arrow, down arrow
	if (keyCode == 38 || keyCode == 40) {
		Event.stop(event);
		var div = $("typein_div");
		if (keyCode == 38) // up
			Ytypein.selectItem(div.matchIndex - 1);
		if (keyCode == 40) // down
			Ytypein.selectItem(div.matchIndex + 1);
		return;
	}
}

Ytypein.isTypeIn = function() {
	return $("typein_div").style.display == 'block';
}

Ytypein.enterTypeIn = function(sele, pattern) {
	// reset data
	Ytypein.resetTypeInData();

	// bind div with select
	var div = $("typein_div");
	div.select = sele;

	// determine upsidedown
	if (Ytypein.determineUpsidedownOrNot()) {
		Ytypein.turnUpsidedown();
	}

	// show div
	Ytypein.showTypeIn();

	// setup pattern
	var ptn = $("typein_pattern");
	ptn.focus();
	ptn.focus(); // double focus to walk around a IE 6 bug
	ptn.value = pattern || "";
	
	window.setTimeout('Ytypein.rematchAll();', 50);
}

Ytypein.leaveTypeIn = function() {
	var div = $("typein_div");
	var sele = div.select;
	var changed = false;

	// prevent re-entering
	if (!Ytypein.isTypeIn() || div.matchCtrl == 'leaving') return;
	div.matchCtrl = 'leaving';

	// hide div
	$("typein_pattern").blur(); // firefox need blur() first to show cursor properly
	Ytypein.hideTypeIn();

	// update selected option
	if (div.optionIndex >= 0) {
		sele.options[div.optionIndex].selected = true;
		changed = true;
	}

	// check upsidedown
	if (div["upsidedown"]) {
		Ytypein.turnUpsidedown();
	}

	// fire 'onchange'
	if (changed)
		sele.fireEvent("onchange");
	
	// focus back to <select>
	sele.focus();
}

Ytypein.resetTypeInData = function() {
	var div = $("typein_div");
	var pattern = $("typein_pattern");
	var matches = $("typein_matches");

	div.select = null;
	div.optionIndex = -1;
	div.matchIndex = -1;
	div.lastPattern = null;
	div.matchCtrl = '';

	pattern.value = "";
	matches.innerHTML = "";
}

Ytypein.showTypeIn = function() {
	var div = $("typein_div");
	var matches = $("typein_matches");
	var shim = $("typein_shim");
	var sele = div.select;

	// adjust font size
	var n = matches.childNodes.length;
	n = n > 7 ? n - 7 : 0;
	n = 100 - Math.floor(n * n / 20);
	n = n > 100 ? 100 : n;
	n = n < 60 ? 60 : n;
	matches.style.fontSize = n + "%";

	// align width with select
	div.style.width = "";
	if (div.offsetWidth <= sele.offsetWidth) {
		div.style.width = sele.offsetWidth + "px";
	}

	var pos = Yutil.abspos(sele);
	var x = pos[0];
	var y = div["upsidedown"] ? pos[1] - div.offsetHeight : pos[1] + sele.offsetHeight;

	Yutil.show(div);
	Yutil.moveTo(div, x, y);

	Yutil.show(shim);
	Yutil.moveTo(shim, x, y);
	Yutil.resizeTo(shim, div.offsetWidth, div.offsetHeight);
}

Ytypein.hideTypeIn = function() {
	var div = $("typein_div");
	var shim = $("typein_shim");

	Yutil.hide(div);
	Yutil.hide(shim);
}

Ytypein.determineUpsidedownOrNot = function() {
	var div = $("typein_div");
	var sele = div.select;
	var winHeight = window.innerHeight || document.documentElement.clientHeight;

	div["upsidedown"] = Yutil.abspos(sele)[1] - document.body.scrollTop > winHeight * 0.66;
	return div["upsidedown"];
}

Ytypein.turnUpsidedown = function() {
	var div = $("typein_div");

	var c;
	var childs = new Array();
	while (div.childNodes.length > 0) {
		c = div.lastChild;
		div.removeChild(c);
		childs.push(c);
	}
	for (var i = 0; i < childs.length; i++) {
		div.appendChild(childs[i]);
	}
}

Ytypein.rematchAll = function(pattern) {
	var typeinDiv = $("typein_div");
	var matchesElem = $("typein_matches");
	var sele = typeinDiv.select;

	if (typeinDiv.matchCtrl == 'matching') {
		typeinDiv.matchCtrl = 'once-more';
		return; // no re-entering
	}
	
	if (!pattern) pattern = $("typein_pattern").value;
	if (pattern == typeinDiv.lastPattern)
		return; // skip repeated match
	
	typeinDiv.matchCtrl = 'matching';
	typeinDiv.lastPattern = pattern;

	// find options that match pattern
	var matches = new Array();
	var secMatches = new Array();
	var options = sele.options;
	var count = 0, pos;
	pattern = pattern.toLowerCase();
	for (var i = 0; i < options.length && count < Ytypein.MAX_MATCHES; i++) {
		pos = options[i].text.toLowerCase().indexOf(pattern);
		if (pos >= 0)
			count++;
		if (pos == 0)
			matches.push(i);
		if (pos > 0)
			secMatches.push(i);
	}
	matches = matches.concat(secMatches);

	typeinDiv.optionIndex = -1;
	typeinDiv.matchIndex = -1;

	// render the matches
	if (0 < count && count < Ytypein.MAX_MATCHES) {
		matchesElem.innerHTML = "";
		for (var i = 0; i < matches.length; i++) {
			var m = ("<div onmouseover='Ytypein.selectItem("+i+")'>" + options[matches[i]].text + "</div>").createElement();
			m.typein_optidx = matches[i];
			matchesElem.appendChild(m);
		}

		// highlight the first match by default
		Ytypein.selectItem(typeinDiv["upsidedown"] ? matches.length - 1 : 0);
	} else {
		matchesElem.innerHTML = matches == 0 ? "No matches." : "A lot of matches ...";
	}

	// show the typein div
	Ytypein.showTypeIn();
	
	// rematch again?
	var onceMore = (typeinDiv.matchCtrl == 'once-more');
	typeinDiv.matchCtrl = '';
	if (onceMore) Ytypein.rematchAll();
}

Ytypein.selectItem = function(matchIndex) {
	var typeinDiv = $("typein_div");
	var matchesElem = $("typein_matches");

	// verify range and make sure there are matches
	if (matchIndex < 0 || matchIndex >= matchesElem.childNodes.length
		|| matchesElem.firstChild.nodeName.toLowerCase() !="div") {
		return;
	}

	// dehighlight the old
	var oldMatchIndex = typeinDiv.matchIndex;
	if (oldMatchIndex >= 0) {
		var oldMatch = matchesElem.childNodes[oldMatchIndex];
		Ytypein.undoHighlightItem(oldMatch);
	}

	// highlight the new
	var match = matchesElem.childNodes[matchIndex];
	Ytypein.highlightItem(match);

	// remember the matchIndex and optionIndex
	typeinDiv.matchIndex = matchIndex;
	typeinDiv.optionIndex = match.typein_optidx;
}

Ytypein.highlightItem = function(itemDiv) {
	itemDiv.style.fontWeight = "bold";
	itemDiv.style.background = "#DDD";
}

Ytypein.undoHighlightItem = function(itemDiv) {
	itemDiv.style.fontWeight = "normal";
	itemDiv.style.background = "#FFF";
}

// enable typein on load
Yutil.callOnLoad(Ytypein.enableTypeInFunction);
